package com.cold.legendary.bprocess.context.manager;

import com.cold.legendary.bprocess.common.constance.BErrorCodeEnum;
import com.cold.legendary.bprocess.common.constance.CommonConstances;
import com.cold.legendary.bprocess.common.exception.BProcessException;
import com.cold.legendary.bprocess.context.annotation.BPoint;
import com.cold.legendary.bprocess.context.annotation.BPointTag;
import com.cold.legendary.bprocess.context.annotation.BProcess;
import com.cold.legendary.bprocess.context.annotation.BSequence;
import com.cold.legendary.bprocess.context.def.BPointDef;
import com.cold.legendary.bprocess.context.def.SequenceClazzDef;
import com.cold.legendary.bprocess.context.enums.PointTagEnum;
import com.cold.legendary.bprocess.context.ins.BArrowIns;
import com.cold.legendary.bprocess.context.ins.BPointIns;
import com.cold.legendary.bprocess.context.ins.BProcessIns;
import com.cold.legendary.bprocess.context.manager.helper.BPHelper;
import com.cold.legendary.bprocess.definition.point.BPointService;
import com.cold.legendary.bprocess.definition.process.BProcessDefinition;
import com.cold.legendary.bprocess.definition.process.dto.BPointDefDto;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author cold
 * @since 2023/3/9
 */
@Slf4j
public class BProcessManager {
    /**
     * 流程实例
     */
    private static ConcurrentHashMap<String, BProcessIns> bprocessInsMap = new ConcurrentHashMap<>();
    /**
     * 单元节点描述
     * key pointId
     */
    private static ConcurrentHashMap<String, BPointDef> bPointDefMap = new ConcurrentHashMap<>();
    /**
     * 自定义序号生成器
     */
    private static SequenceClazzDef sequenceClazzDef = SequenceClazzDef.builder().build();


    /**
     * 加载所有节点描述
     *
     * @param applicationContext spring应用上下文
     */
    public static void loadPoints(ApplicationContext applicationContext) {
        //获取指定注解的bean
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(BPoint.class);
        beanMap.forEach((beanName, beanObj) -> {
            if (!(beanObj instanceof BPointService)) {
                log.error("检测到带有@BPoint注解的bean,未实现BPointService接口!请实现!error beanName={}", beanName);
                throw new BProcessException(BErrorCodeEnum.SYS_NO_BPOINT_ERROR);
            }
            BPointService bPointService = (BPointService) beanObj;
            BPoint bPointAnno = beanObj.getClass().getAnnotation(BPoint.class);

            PointTagEnum pointTagEnum = PointTagEnum.MID;
            BPointTag bPointTagAnno = beanObj.getClass().getAnnotation(BPointTag.class);
            if (null != bPointTagAnno) {
                pointTagEnum = bPointTagAnno.pointTagEnum();
            }
            bPointDefMap.put(bPointAnno.pointId(),
                    BPointDef.builder()
                            .pointId(bPointAnno.pointId())
                            .pointName(bPointAnno.pointName())
                            .callWayEnum(bPointAnno.pointCallWay())
                            .bPointService(bPointService)
                            .pointTagEnum(pointTagEnum)
                            .build());
        });
    }


    /**
     * 加载所有流程
     *
     * @param applicationContext spring应用上下文
     */
    public static void loadProcesses(ApplicationContext applicationContext) {
        //获取指定注解的bean
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(BProcess.class);
        beanMap.forEach((beanName, beanObj) -> {
            if (!(beanObj instanceof BProcessDefinition)) {
                log.error("检测到带有@BProcess注解的bean,未实现BProcessDefinition接口!请实现!error beanName={}", beanName);
                throw new BProcessException(BErrorCodeEnum.SYS_NO_BPROCESS_ERROR);
            }
            BProcess bProcessAnno = beanObj.getClass().getAnnotation(BProcess.class);
            BProcessDefinition bProcessDefinition = (BProcessDefinition) beanObj;
            List<String> bPointDefs = bProcessDefinition.definePoints();
            if (CollectionUtils.isEmpty(bPointDefs)) {
                log.error("检测到process无定义的业务节点,请检查流程定义@BProcess的类!beanName={}, processId={}", beanName, bProcessAnno.processId());
                throw new BProcessException(BErrorCodeEnum.SYS_BPROCESS_NO_BPOINT_ERROR);
            }

            List<BPointDefDto> bPointDefDtos = new ArrayList<>();
            for (int i = 1; i <= bPointDefs.size(); i++) {
                bPointDefDtos.add(
                        BPointDefDto.builder()
                                .sort(i)
                                .pointId(bPointDefs.get(i - 1))
                                .build());
            }

            //定义的业务节点进行排序
            bPointDefDtos.sort(Comparator.comparing(BPointDefDto::getSort));

            //自动添加上开始节点和结束节点
            bPointDefDtos.add(0, BPointDefDto.builder().pointId(CommonConstances.START_POINT_SERVICE).build());
            bPointDefDtos.add(BPointDefDto.builder().pointId(CommonConstances.END_POINT_SERVICE).build());

            //节点定义压入栈中
            Stack<BPointDef> stack = BPHelper.buildBPointDefStack(bProcessAnno.processId(), bPointDefDtos, bPointDefMap);

            //递归构建业务节点箭头链条,返回箭头
            BArrowIns linkToStartBArrowIns = buildPointArrowLink(null, stack);
            if (null == linkToStartBArrowIns) {
                log.error("构建出流程链引用为空!beanName={}, processId={}", beanName, bProcessAnno.processId());
                throw new BProcessException(BErrorCodeEnum.SYS_BUILD_ERROR);
            }
            //流程实例
            BProcessIns bProcessIns = BProcessIns.builder()
                    .processId(bProcessAnno.processId())
                    .processName(bProcessAnno.processName())
                    .bizScene(bProcessAnno.bizScene())
                    .startBPointIns(linkToStartBArrowIns.getNextBPointIns())
                    .build();

            bprocessInsMap.put(bProcessAnno.processId(), bProcessIns);
        });
    }

    /**
     * 递归构建节点箭头链条,返回箭头
     *
     * @param bPointIns
     * @param stack
     * @return
     */
    private static BArrowIns buildPointArrowLink(BPointIns bPointIns, Stack<BPointDef> stack) {
        if (stack.isEmpty()) {
            return BPHelper.createBArrowIns(bPointIns);
        }
        BPointDef bPointDef = stack.pop();
        BArrowIns tmpBArrowIns = BPHelper.createBArrowIns(bPointIns);
        BPointIns tmpBPointIns = BPHelper.createBPointIns(bPointDef, tmpBArrowIns);

        return buildPointArrowLink(tmpBPointIns, stack);
    }


    /**
     * 加载sequence的class定义
     *
     * @param applicationContext spring应用上下文
     */
    public static void loadSequenceClazzDef(ApplicationContext applicationContext) {
        //获取指定注解的bean
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(BSequence.class);
        if (beanMap.size() <= 0) {
            log.error("使用loan-process框架,请务必实现配套sequence逻辑类!检测到无@BSequence注解的实现类!");
            throw new BProcessException(BErrorCodeEnum.DB_OPERATOR_ERROR);
        } else if (beanMap.size() > 1) {
            log.error("检测到@BSequence实现类有多个,loan-process将随机获取一个使用!请检查是否正确!");
        }
        Map.Entry<String, Object> entry = beanMap.entrySet().stream().findFirst().orElse(null);
        if (null == entry) {
            log.error("使用loan-process框架,请务必实现配套sequence逻辑类!检测到无@BSequence注解的实现类!");
            throw new BProcessException(BErrorCodeEnum.SYS_NO_SEQUENCE_ERROR);
        }
        Object bean = entry.getValue();
        Class clazz = bean.getClass();
        Method[] methods = clazz.getMethods();
        boolean flag = false;
        for (Method method : methods) {
            //筛选包含注解的方法
            if (method.isAnnotationPresent(BSequence.class)) {
                flag = true;

                if (!String.class.getName().equals(method.getReturnType().getName())) {
                    log.error("检测到@BSequence注解的实现方法返回值类型不为java.lang.String!请检查返回值!");
                    throw new BProcessException(BErrorCodeEnum.SYS_NOT_STRING_ERROR);
                }

                sequenceClazzDef = SequenceClazzDef.builder()
                        .sequenceBean(bean)
                        .method(method)
                        .build();
            }
        }
        if (!flag) {
            log.error("检测到无@BSequence注解的实现方法,请实现之!");
            throw new BProcessException(BErrorCodeEnum.SYS_NO_SEQUENCE_IMPL_ERROR);
        }
    }

    /**
     * 获取流程实例
     *
     * @param processId 流程id
     * @return 节点实例
     */
    public static BPointIns getStartPoint(String processId) {
        BProcessIns bProcessIns = getBProcessIns(processId);
        return bProcessIns.getStartBPointIns();
    }

    public static BProcessIns getBProcessIns(String processId) {
        BProcessIns bProcessIns = bprocessInsMap.get(processId);
        if (null == bProcessIns) {
            log.error("流程实例不存在!processId={}", processId);
            throw new BProcessException(BErrorCodeEnum.SYS_NO_PROCESSINS_IMPL_ERROR);
        }
        return bProcessIns;
    }

    /**
     * 获取Sequence
     *
     * @return sequenceId
     */
    public static String getSequence() {
        try {
            Object sequenceBean = sequenceClazzDef.getSequenceBean();
            Method method = sequenceClazzDef.getMethod();
            //反射调用
            Object result = method.invoke(sequenceBean);
            //加载实现类的时候判断过返回值类型一定是java.lang.String,此处可以直接强制转换
            return (String) result;
        } catch (Exception e) {
            log.error("序号产生异常!", e);
            throw new BProcessException(BErrorCodeEnum.SYS_CREATE_SERIAL_ERROR);
        }
    }

    public static BPointIns getCurBPointIns(String processId, String curPointId) {
        BProcessIns bProcessIns = getBProcessIns(processId);
        BPointIns startBPointIns = bProcessIns.getStartBPointIns();
        return getCurrentBPointIns(startBPointIns, curPointId);
    }

    private static BPointIns getCurrentBPointIns(BPointIns bPointIns, String curPointId) {
        if (null == bPointIns || StringUtils.isEmpty(curPointId)) {
            return null;
        }
        if (bPointIns.getPointId().equals(curPointId)) {
            return bPointIns;
        }
        if (null == bPointIns.getNextBArrowIns()) {
            return null;
        }
        BPointIns nextBPointIns = bPointIns.getNextBArrowIns().getNextBPointIns();
        return getCurrentBPointIns(nextBPointIns, curPointId);
    }


    public static Pair<Integer, BPointIns> getBPonitOrder(String processId, String curPointId) {
        BProcessIns bProcessIns = getBProcessIns(processId);
        BPointIns startBPointIns = bProcessIns.getStartBPointIns();
        int order = 0;
        return getCurrentBPointOrder(order, startBPointIns, curPointId);
    }

    private static Pair<Integer, BPointIns> getCurrentBPointOrder(Integer order, BPointIns bPointIns, String curPointId) {
        if (null == bPointIns || StringUtils.isEmpty(curPointId)) {
            return new Pair<>(order, bPointIns);
        }
        if (bPointIns.getPointId().equals(curPointId)) {
            return new Pair<>(order, bPointIns);
        }
        if (null == bPointIns.getNextBArrowIns()) {
            return new Pair<>(order, bPointIns);
        }
        order ++;
        order ++;
        BPointIns nextBPointIns = bPointIns.getNextBArrowIns().getNextBPointIns();
        return getCurrentBPointOrder(order, nextBPointIns, curPointId);
    }

    public static BPointIns getPreviousCurBPointInsOnProcess(String processId, String triggerPointId) {
        BProcessIns bProcessIns = getBProcessIns(processId);
        BPointIns startBPointIns = bProcessIns.getStartBPointIns();
        return getPreviousBPointIns(startBPointIns, triggerPointId);
    }

    private static BPointIns getPreviousBPointIns(BPointIns bPointIns, String curPointId) {
        if (null == bPointIns || StringUtils.isEmpty(curPointId)) {
            return null;
        }
        if (null == bPointIns.getNextBArrowIns()) {
            return null;
        }
        BPointIns nextBPointIns = bPointIns.getNextBArrowIns().getNextBPointIns();
        if (null == nextBPointIns) {
            return null;
        }
        if (nextBPointIns.getPointId().equals(curPointId)) {
            return bPointIns;
        }
        return getPreviousBPointIns(nextBPointIns, curPointId);
    }

    public static ConcurrentHashMap<String, BProcessIns> getBprocessInsMap() {
        return bprocessInsMap;
    }
}
